//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <mantle.h>
#include "exception.h"

#ifdef _neptune
#include "nu_api.h"

#define NUCLEUS_LOWADDRBOTTOM   0
#define NUCLEUS_LOWADDRTOP      0x11000000
#define NUCLEUS_HIGHADDRBOTTOM  0xE0000000
#define NUCLEUS_HIGHADDRTOP     0xFFFFFFFF

#define IsNucleusAddress(x) (x < NUCLEUS_LOWADDRTOP) || (x > NUCLEUS_HIGHADDRBOTTOM)
void NucleusDbgHandler(InterruptContext *pContext, int type)
{
    if (IsNucleusAddress(pContext->pc)) {
        nu_ExceptionHandler((Uint32 *)pContext, type);
    }
    return;
}
#endif

EXTERN int CDECL DebuggerHandler(
                    vector_t vector,
                    InterruptContext *pContext,
                    uint_t uPageFaultAddr);
/*
STATIC const char *s_szExceptionDescriptions[] = {
    "Undefined Instruction",
    "Prefetch Abort",
    "Data Abort"
};

STATIC INLINE const char *GetExceptionDescription(vector_t vector)
{
    assert(vector < sizeof(s_szExceptionDescriptions) / sizeof(const char *));

    return s_szExceptionDescriptions[vector];
}
*/

static bool SendOutDebugException(vector_t vector,
		InterruptContext *pContext, UInt32 faultAddress)
{
    assert(GetCurrentProcess());
    UInt32 reason = 0;
    UInt32 handle[2];

    reason = DEBUGGEE_EXCEPTION;
    handle[0] = vector;

    switch (vector) {
        case ExceptionVector_DataAbort:
        case ExceptionVector_PrefetchAbort:
            handle[1] = faultAddress;
            break;

        case ExceptionVector_UndefinedInstruction:
            handle[1] = pContext->pc;
            break;

        default:
            assert0();
            break;
    }

    return TryToSendReceiveDebugMsg(reason, (UInt32)handle);
}

DECL_ASMLINKAGE void CDECL HandleException(vector_t vector,
    bool_t bCheckWritable, InterruptContext *pContext)
{
    register bool_t bHandlePageFault = FALSE;
    register virtaddr_t faultAddress = NULL;

    switch (vector) {
        case ExceptionVector_DataAbort:
//        kprintf("fault status: %x, fault address: %x\n",
//                GetFaultStatus(), GetFaultAddress());
#ifdef _neptune
//            kprintf("%s, %d\n", __FILE__, __LINE__);
            NucleusDbgHandler(pContext, 0);
#endif

            faultAddress = GetFaultAddress();
            bHandlePageFault = TRUE;
            break;
        case ExceptionVector_PrefetchAbort:
//        kprintf("fault address: %x\n", pContext->pc);
            faultAddress = (virtaddr_t)pContext->pc;
            bHandlePageFault = TRUE;
#ifdef _neptune
            NucleusDbgHandler(pContext, 1);
#endif
            break;
        case ExceptionVector_UndefinedInstruction:
            /*
             * if we met a debugbreak instruction, we should
             * correct pc back to the address where the exception occured.
             * because we don't use thumb mode, pc should always be -4.
             *
             * for other instructions which we don't recognize, just keep
             * the pc as it is, and not correct it.
             *
             * if we need to deal with different kinds of undefined
             * instructions, we could call proper ExceptionHandlers
             * which fit for each instruction.
             */
            switch(*(uint32_t *)(pContext->pc - 4)) {
              //debug break instruction
              case ELASTOS_GDBSERVER_BREAKPOINT: //fall through
              case ELASTOS_GDB_BREAKPOINT: //fall through
              case ELASTOS_HARD_BREAKPOINT:
                pContext->pc -= 4;
                break;

              default:
                break;
            }

            break;
    }

    if (bHandlePageFault) {
        register CProcess *pCurrentProcess = GetCurrentProcess();

        if (NULL != pCurrentProcess) {
            ECode ec = pCurrentProcess->PageFault(
                (uint_t)faultAddress, pContext->sp,
                IS_IN_KERNEL_MODE(pContext), bCheckWritable);
            if (SUCCEEDED(ec)) return;
        }
    }

//    kprintf("*EXCEPTION* %s\n", GetExceptionDescription(vector));

//    kprintf("Process:%x, Thread: %x\n",
//            GetCurrentProcess(), GetCurrentThread());
//
//    kprintf("fault status: %x, fault address: %x, check writable: %s\n",
//            GetFaultStatus(), faultAddress,
//            bCheckWritable? "true" : "false");

    if (!IS_IN_KERNEL_MODE(pContext)) {
    	if (SendOutDebugException(vector, pContext, (UInt32) faultAddress)) {
    		return;
    	}
    }
    /*
     * If DebuggerHandler waits for eladbg.exe to attach and returns, try
     * to send out debug event again.
     */
    if (!DebuggerHandler(vector, pContext, (uint_t) faultAddress)) {
    	SendOutDebugException(vector, pContext, (UInt32) faultAddress);
    }
}
